#include "httprequest.h"
using namespace std;

//对应页面
const unordered_set<string> HttpRequest::DEFAULT_HTML{
            "/index","/list","/register",
             "/welcome", "/video", "/picture", };
//对应接口
const unordered_set<string> HttpRequest::DEFAULT_ACCESS{
"/login","/list/get","/upload" };

const unordered_map<string, int> HttpRequest::DEFAULT_HTML_TAG {
            {"/register.html", 0}, {"/login.html", 1},  };

void HttpRequest::Init() {
    method_ = path_ = version_ = body_ = "";
    state_ = REQUEST_LINE;
    header_.clear();
    post_.clear();
}

//判断请求是否是keepalive的
bool HttpRequest::IsKeepAlive() const {
  if(header_.count("Connection")) {
    return header_.find("Connection")->second == "keep-alive" && version_ == "1.1";
  }
  return false;
}

//解析请求行,请求头,请求体(表单数据)
//纯页面没有请求体,此时请求头也没有Content-Type
bool HttpRequest::parse(Buffer& buff) {
  const char CRLF[] = "\r\n";
  if(buff.ReadableBytes() <= 0) {
      return false;
  }
  while(buff.ReadableBytes() && state_ != FINISH) {
    //从第一次出现\r\n的位置处截断,lineEnd是后面的字符串的首地址,后面的字符串头部是第一次出现的\r\n
    const char *lineEnd = search(buff.Peek(), buff.BeginWriteConst(), CRLF, CRLF + 2);
    //将buff.Peek()(地址值)和lineEnd(地址值)之间的字符串给line
    //line + lineEnd是完整的buff.Peek()
    std::string line(buff.Peek(), lineEnd);

    switch(state_){
      case REQUEST_LINE:
        if(!ParseRequestLine_(line)) {
          return false;
        }
        ParsePath_();
        break;    
      case HEADERS:
        ParseHeader_(line);//HTTP格式,最后一个请求头后面是两个\r\n
        if(buff.ReadableBytes() == 2) {//请求没有请求体
          buff.RetrieveUntil(lineEnd + 2);
          state_ = FINISH;
        }
        break;
      case BODY:
        ParseBody_(lineEnd,buff);
        break;
      default:
        break;
    }
    if(lineEnd == buff.BeginWrite()) {//此时数据包都解析完了
      buff.RetrieveUntil(lineEnd);//记得要清空(给客户端发消息之后,还会检查readBuf是否可读)
      break;
    }
    if(state_ != FINISH){
      buff.RetrieveUntil(lineEnd + 2);//将readPos_移到(lineEnd+2)的位置
    }
  }
  return true;
}

//根据解析请求行得到的path_,得到页面名字path_.html
//这些情况就拼接html页面,其他情况不变
void HttpRequest::ParsePath_() {
  if(path_ == "/"){
    path_ = "/login.html";
  }else{
    for(auto &item: DEFAULT_HTML) {
      if(item == path_) {
        path_ += ".html";
        break;
      }
    }
  }
}

//根据请求行内容进行解析提取字段
//"GET /list/get HTTP/1.1"
//"POST /register HTTP/1.1"
//subMatch[0]是匹配整个正则的字符串
bool HttpRequest::ParseRequestLine_(const string& line) {
    regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
    smatch subMatch;
    if(regex_match(line, subMatch, patten)) {   
        method_ = subMatch[1];
        path_ = subMatch[2];

        //处理get请求url后面带参数的
        size_t pos = path_.find('?');
        if(pos != string::npos){
          path_ = path_.substr(0,pos);
        }

        version_ = subMatch[3];
        state_ = HEADERS;
        return true;
    }
    LOG_ERROR("RequestLine Error");
    return false;
}

//将请求头的每个字段及值解析出来,做成键值对,装到header_中
void HttpRequest::ParseHeader_(const string& line) {
    regex patten("^([^:]*): ?(.*)$");
    smatch subMatch;
    if(regex_match(line, subMatch, patten)) {
        std::string::size_type index;
        //这种情况拆成两个请求头
        if(subMatch[1].str() == "Content-Type" && (index = subMatch[2].str().find("; ")) != string::npos){
          header_[subMatch[1]] = subMatch[2].str().substr(0,index);//请求头1,从索引0开始,截取index长度
          int eq_index = subMatch[2].str().find('=',index);//请求头2
          string eq_key = subMatch[2].str().substr(index+2,eq_index-(index+2));
          header_[eq_key] = subMatch[2].str().substr(eq_index+1);
        }else{
          header_[subMatch[1]] = subMatch[2];
        }
    }
    else {
        state_ = BODY;
    }
}

//解析请求体(情况很多,JSON,表单,文件等)
void HttpRequest::ParseBody_(const char *lineEnd,Buffer& buff) {
  //上传文件
  if(method_ == "POST" && header_["Content-Type"] == "multipart/form-data"){
    const char *pos=strstr(lineEnd,"filename=\"");
    char file_name[50];
    int i;
    for(i = 10;pos[i] != '\"';i++){file_name[i-10] = pos[i];}
    file_name[i-10] = '\0';

    char dst_file[strlen("user_data/u_")+strlen(header_["user"].c_str())
    +strlen(header_["cur"].c_str())+strlen(file_name)+1] = "user_data/u_";
    strcat(dst_file,header_["user"].c_str());
    strcat(dst_file,header_["cur"].c_str());
    strcat(dst_file,file_name);

    int fd = open(dst_file,O_CREAT|O_TRUNC|O_WRONLY,0600);
    if(fd == -1){perror("open() failed");}else{
      std::string str(buff.Peek(), buff.ReadableBytes());
      int startIndex = str.find("\r\n\r\n")+4;
      int endIndex = str.find("\r\n--");
      std::string new_str = str.substr(startIndex,(endIndex-startIndex));
      int ret = write(fd,new_str.c_str(),(endIndex-startIndex));
      if(ret == -1){perror("write()");printf("%d\n",errno);}else{
        //插入mysql记录
        MYSQL* sql;
        SqlConnRAII temVal(&sql, SqlConnPool::Instance());
        assert(sql);

        struct stat refs;
        stat(dst_file,&refs);
        off_t file_size = refs.st_size;

        //将SQL语句写入到sql_str数组中
        char sql_str[256];
        snprintf(sql_str, 256, "INSERT INTO user_files values(null,'%s','%s',now(),%d,'测试使用',0,'%s')",
        header_["user"].c_str(),file_name,file_size,header_["cur"].c_str());

        if(mysql_query(sql, sql_str)) {//Zero for success. Nonzero if an error occurred.
          LOG_ERROR("mysql_query() error");
        }
      }
      buff.RetrieveUntil(buff.BeginWriteConst());
      close(fd);
    }
  }
  //接口请求
  if(method_ == "POST" && header_["Content-Type"] == "application/json"){
    body_.assign(buff.Peek(),buff.BeginWriteConst());//后面交给json库去解析
    lineEnd = buff.BeginWrite();//清空buff内容
  }
  state_ = FINISH;
}



int HttpRequest::ConverHex(char ch) {
    if(ch >= 'A' && ch <= 'F') return ch -'A' + 10;
    if(ch >= 'a' && ch <= 'f') return ch -'a' + 10;
    return ch;
}

//废弃
void HttpRequest::ParsePost_() {
    if(method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") {
        ParseFromUrlencoded_();
        // 判断map中是否存在path_这个键,处理登陆或注册行为
        if(DEFAULT_HTML_TAG.count(path_)) {
          //返回键path_对应的值
            int tag = DEFAULT_HTML_TAG.find(path_)->second;
            LOG_DEBUG("Tag:%d", tag);
            if(tag == 0 || tag == 1) {
                bool isLogin = (tag == 1);//tag为1时,isLogin为true
                if(UserVerify(post_["username"], post_["password"], isLogin)) {
                    path_ = "/welcome.html";
                } 
                else {
                    path_ = "/error.html";
                }
            }
        }
    }
}
//废弃
void HttpRequest::ParseFromUrlencoded_() {
    if(body_.size() == 0) { return; }

    string key, value;
    int num = 0;
    int n = body_.size();
    int i = 0, j = 0;

    for(; i < n; i++) {
        char ch = body_[i];
        switch (ch) {
        case '=':
            key = body_.substr(j, i - j);
            j = i + 1;
            break;
        case '+':
            body_[i] = ' ';
            break;
        case '%':
            num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]);
            body_[i + 2] = num % 10 + '0';
            body_[i + 1] = num / 10 + '0';
            i += 2;
            break;
        case '&':
            value = body_.substr(j, i - j);
            j = i + 1;
            post_[key] = value;
            LOG_DEBUG("%s = %s", key.c_str(), value.c_str());
            break;
        default:
            break;
        }
    }
    assert(j <= i);
    if(post_.count(key) == 0 && j < i) {
        value = body_.substr(j, i - j);
        post_[key] = value;
    }
}


//废弃(登陆or注册)
bool HttpRequest::UserVerify(const string &name, const string &pwd, bool isLogin) {
  if(name == "" || pwd == "") { return false; }
  MYSQL* sql;
  SqlConnRAII temVal(&sql, SqlConnPool::Instance());
  assert(sql);

  //将SQL语句写入到sql_str数组中
  char sql_str[256];
  snprintf(sql_str, 256, "SELECT username, password FROM user WHERE username='%s' LIMIT 1", name.c_str());

  if(mysql_query(sql, sql_str)) {//Zero for success. Nonzero if an error occurred.
    LOG_ERROR("mysql_query() error");
  }
  MYSQL_RES *res = mysql_store_result(sql);
  unsigned int j = mysql_num_fields(res);//结果集的列数

  //表中有数据,就一条,因为注册时,用户名不可重复
  if(MYSQL_ROW row = mysql_fetch_row(res)){
    LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);
    string password(row[1]);
    if(isLogin){//登陆
      if(pwd == password){//校验密码
        mysql_free_result(res);
        return true;
      }else{
        LOG_DEBUG("登陆密码错误!");
        mysql_free_result(res);      
        return false;
      }
    }else{//注册
      LOG_DEBUG("用户名已存在,请换一个用户名注册");
      mysql_free_result(res);     
      return false;
    }
  }
  mysql_free_result(res);

  //表中没数据
  if(!isLogin){//注册
    snprintf(sql_str, 256,"INSERT INTO user(username, password) VALUES('%s','%s')", name.c_str(), pwd.c_str());
    if(mysql_query(sql, sql_str)) { 
      LOG_ERROR("mysql_query() error");
    }
    return true;
  }else{//登陆
    LOG_DEBUG("用户名不存在");
    return false;
  }
}

std::string HttpRequest::path() const{
    return path_;
}

std::string& HttpRequest::path(){
    return path_;
}
std::string HttpRequest::method() const {
    return method_;
}

std::string HttpRequest::version() const {
    return version_;
}

std::string HttpRequest::GetPost(const std::string& key) const {
    assert(key != "");
    if(post_.count(key) == 1) {
        return post_.find(key)->second;
    }
    return "";
}

std::string HttpRequest::GetPost(const char* key) const {
    assert(key != nullptr);
    if(post_.count(key) == 1) {
        return post_.find(key)->second;
    }
    return "";
}
std::string& HttpRequest::GetBody(){
  return body_;
}